package com.lagou.edu.mvcframework.servlet;

import com.lagou.edu.mvcframework.Annotaions.*;
import com.lagou.edu.mvcframework.pojo.Handler;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 自定义前端控制器
 */
public class LgDispatcherServlet extends HttpServlet {

    private Properties properties = new Properties();

    private List<String> classNames = new ArrayList<>();

    private Map<String, Object> ioc = new HashMap<>();

    private List<Handler> handlerMapping = new ArrayList<>();

    /**
     * 初始化
     *
     * @param config
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        //1. 加载配置文件
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);

        //2. 扫描相关的类,扫描注解
        doScan(properties.getProperty("scanPackage"));

        //3. 初始化ioc容器,基于注解的形式
        doInstance();

        //4. 维护bean之间的依赖关系
        doAutoWired();

        //5. 构造一个HandlerMapping处理器适配器,将配置好的url和handler方法建立映射
        initHandlerMapping();

        System.out.println("lagoumvc 初始化完成 ==============");

    }

    /**
     * 构造一个HandlerMapping处理器适配器
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Object value = entry.getValue();
            Class<?> aClass = value.getClass();
            //没有controller注解
            if (!aClass.isAnnotationPresent(LagouController.class)) {
                continue;
            }

            String baseUrl = "";
            List<String> permissionList = new ArrayList<>();
            if (aClass.isAnnotationPresent(LagouRequestMapping.class)) {
                //获取到controller上的url
                baseUrl = aClass.getAnnotation(LagouRequestMapping.class).value();
            }
            //获取类上的权限列表
            if (aClass.isAnnotationPresent(LagouSecurity.class)) {
                String[] values = aClass.getAnnotation(LagouSecurity.class).value();
                if (values.length > 0) {
                    permissionList.addAll(List.of(values));
                }
            }

            //获取handler方法
            Method[] declaredMethods = aClass.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                if (!declaredMethod.isAnnotationPresent(LagouRequestMapping.class)) {
                    //没有注解不进行处理
                    continue;
                }
                //获取方法上的权限列表
                if (declaredMethod.isAnnotationPresent(LagouSecurity.class)) {
                    String[] values = declaredMethod.getAnnotation(LagouSecurity.class).value();
                    if (values.length > 0) {
                        permissionList.addAll(List.of(values));
                    }
                }
                LagouRequestMapping annotation = declaredMethod.getAnnotation(LagouRequestMapping.class);
                String methodUrl = annotation.value();
                //最终拼接的url
                String url = baseUrl + methodUrl;
                //把method所有信息和url封装成一个Handler
                Handler handler = new Handler(value, declaredMethod, Pattern.compile(url));
                handler.setPermissionList(permissionList);

                //计算方法的参数位置信息
                Parameter[] parameters = declaredMethod.getParameters();
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    Class<?> type = parameter.getType();
                    if (type == HttpServletRequest.class || type == HttpServletResponse.class) {
                        //原生api,参数名称直接写类名
                        handler.getParamIndexMapping().put(type.getSimpleName(), i);
                    } else {
                        //参数名
                        handler.getParamIndexMapping().put(parameter.getName(), i);
                    }
                }

                //建立起url和method的映射关系,并存储起来
                handlerMapping.add(handler);
            }
        }
    }

    /**
     * 依赖注入
     */
    private void doAutoWired() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            Class<?> aClass = value.getClass();
            Field[] declaredFields = aClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if (!declaredField.isAnnotationPresent(LagouAutowried.class)) {
                    //没有注解
                    continue;
                }
                LagouAutowried annotation = declaredField.getAnnotation(LagouAutowried.class);
                //注解上的值
                String annValue = annotation.value();
                if ("".equals(annValue.trim())) {
                    //没有配置注解值,根据类型获取名称
                    annValue = declaredField.getType().getName();
                }
                //暴力反射
                declaredField.setAccessible(true);
                //给属性赋值
                try {
                    declaredField.set(value, ioc.get(annValue));
                    ioc.put(key, value);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 实例化ioc容器
     */
    private void doInstance() {
        if (classNames.isEmpty()) {
            return;
        }
        for (String className : classNames) {
            try {
                //通过全限定类名获取字节码文件,并实例化
                Class<?> aClass = Class.forName(className);
                if (aClass.isAnnotationPresent(LagouController.class)) {
                    //controller,简单点只取类名的首字母小写作为id,存入ioc容器中
                    String simpleName = aClass.getSimpleName();
                    //首字母小写
                    simpleName = lowerFirst(simpleName);
                    ioc.put(simpleName, aClass.newInstance());
                } else if (aClass.isAnnotationPresent(LagouService.class)) {
                    //service
                    LagouService annotation = aClass.getAnnotation(LagouService.class);
                    //获取注解的值
                    String value = annotation.value();
                    Object obj = aClass.newInstance();
                    if (!"".equals(value.trim())) {
                        //注解值作为id
                        ioc.put(value, obj);
                    } else {
                        //类名首字母小写作为id
                        value = lowerFirst(aClass.getSimpleName());
                        ioc.put(value, obj);
                    }

                    //service层往往是有接口的，面向接口开发，此时再以接口名为id，放入一份对象到ioc中，便于后期根据接口类型注入
                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        ioc.put(anInterface.getName(), obj);
                    }
                } else {
                    continue;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 首字母转大写
     *
     * @param str
     * @return
     */
    private String lowerFirst(String str) {
        char[] chars = str.toCharArray();
        if ('A' <= chars[0] && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }

    /**
     * 扫描包
     *
     * @param scanPackage
     */
    private void doScan(String scanPackage) {
        //获取到路径
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        File pack = new File(scanPackagePath);
        File[] files = pack.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                //递归
                doScan(scanPackage + "." + file.getName());
            } else if (file.getName().endsWith(".class")) {
                //class文件 获取到类的全限定类名,存储起来
                String className = scanPackage + "." + file.getName().replaceAll(".class", "");
                classNames.add(className);
            }
        }
    }

    /**
     * 加载配置文件
     *
     * @param contextConfigLocation
     */
    private void doLoadConfig(String contextConfigLocation) {
        //利用类加载器,读取配置文件,读到内存中,并获取到文件流
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            //解析成properties
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    /**
     * 处理具体业务逻辑
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取handler
        Handler handler = getHandler(req);
        //handler为空,直接404
        resp.setHeader("Content-type", "text/html;charset=UTF-8");

        if (handler == null) {
            resp.getWriter().write("404 not found");
            return;
        }
        //获取需要执行的handler方法
        Method method = handler.getMethod();
        //获取执行方法的参数类型
        Class<?>[] parameterTypes = method.getParameterTypes();
        //创建一个长度一致的数组,存放执行方法需要的参数
        Object[] parameterList = new Object[parameterTypes.length];


        String username = null;
        //获取请求中的所有参数
        Map<String, String[]> parameterMap = req.getParameterMap();

        //之前存好的参数与位置映射
        Map<String, Integer> paramIndexMapping = handler.getParamIndexMapping();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            //将多个参数通过逗号拼接在一起
            String value = String.join(",", entry.getValue());
            if ("username".equals(entry.getKey())) {
                //获取username的参数
                username = value;
            }
            if (paramIndexMapping.containsKey(entry.getKey())) {
                //获取位置关系映射,并赋值
                Integer index = paramIndexMapping.get(entry.getKey());
                parameterList[index] = value;
            }
        }

        //权限拦截
        List<String> permissionList = handler.getPermissionList();
        if (!permissionList.isEmpty()) {
            //需要拦截
            if (!permissionList.contains(username)) {
                //如果username参数不在权限列表中,页面输出没有权限
                resp.getWriter().write("无权限访问");
                return;
            }
        }

        //HttpServletRequest和HttpServletResponse单独处理
        int requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        parameterList[requestIndex] = req;

        int responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        parameterList[responseIndex] = resp;

        try {
            //执行方法
            Object invoke = method.invoke(handler.getController(), parameterList);
            resp.getWriter().write(invoke.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据请求url从HandlerMapping获取handler
     *
     * @param req
     * @return
     */
    private Handler getHandler(HttpServletRequest req) {
        if (handlerMapping.isEmpty()) {
            return null;
        }
        String url = req.getRequestURI();
        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(url);
            if (matcher.matches()) {
                return handler;
            }
        }
        return null;
    }
}
